/* Copyright (c) 2009-2010, Sean A.O. Harney <sharney3@gmail.com> */

#include "omf.h"

/* prototypes */
void _print_permutation(uint8_t * perm, size_t perm_len);
uint8_t *_get_next_permutation(size_t * perm_len);


void stmt_process(ASL_NODE_P stmt)
{
    debug2_printf("Processing Statement: %s\n",
		  stmt_type_to_str(stmt->type));
    switch (stmt->type)
      {
      case STMT_FUZZDATA:
	  stmt_process_fuzzdata(&(stmt->params.STMT_FUZZDATA));
	  break;
      case STMT_TARGET:
	  stmt_process_target(&(stmt->params.STMT_TARGET));
	  break;
      case STMT_START:
	  stmt_process_start(stmt->params.STMT_START.start_arg);
	  break;
      case STMT_FLAG:
	  stmt_process_flag(&(stmt->params.STMT_FLAG));
	  break;
      case STMT_SEND:
	  //TODO: here or in .y perhaps, assert target socket or target command stdin with send/expect
	  stmt_process_send(&(stmt->params.STMT_SEND));
	  break;
      case STMT_EXPECT:
	  stmt_process_expect(&(stmt->params.STMT_EXPECT));
	  break;
      case STMT_FUNCTIONCALL:
	  stmt_process_functioncall(stmt->params.STMT_FUNCTIONCALL.
				    symname);
	  break;
      case STMT_PRINT:
	  stmt_process_print(stmt->params.STMT_PRINT.msg);
      case STMT_INCLUDE:
	  stmt_process_include(stmt->params.STMT_INCLUDE.file);
	  break;
      case STMT_SHELL:
	  stmt_process_shell(stmt->params.STMT_INCLUDE.file);
	  break;
      default:
	  fprintf(stderr, "asl_process_stmt(): unknown stmt type: %d\n",
		  stmt->type);
      }
}


void stmt_process_fuzzdata(struct st_stmt_fuzzdata *stmt_fuzzdata)
{
    glob.vm_state.fuzzdata_state = stmt_fuzzdata;
}

void stmt_process_target(struct st_stmt_target *stmt_target)
{
    glob.vm_state.target_state = stmt_target;
}

//pass start_arg = -1 if none given.
void stmt_process_start(int start_arg)
{
    uint8_t *perm, *stdin_perm;
    size_t perm_len, stdin_perm_len;

    struct st_vm_state *vm_state = &(glob.vm_state);
    struct st_stmt_fuzzdata *fuzzdata_state = vm_state->fuzzdata_state;
    struct st_stmt_target *target_state = vm_state->target_state;

    int max_connections = start_arg;	/* for target socket */
    int max_proc = start_arg;	/* for other targets */
#define DEFAULT_MAX_PROC 10
#define DEFAULT_MAX_CONNECTIONS 1
    if (start_arg == -1)
      {
	  if (target_state->socket)
	      max_connections = DEFAULT_MAX_CONNECTIONS;
	  else
	      max_proc = DEFAULT_MAX_PROC;
      }


    if (debug_level == 0)
      {
	  fprintf(stderr, "Hash mark printing on (%d/hash mark).\n",
		  HASH_MARK_RATIO);
	  fflush(stderr);
      }


    if (fuzzdata_state->pattern != NULL)
	regldg_launch(fuzzdata_state->pattern,
		      fuzzdata_state->pattern_maxlen);
    else if (fuzzdata_state->file != NULL)
	dictfile_launch(fuzzdata_state->file);
    else if (fuzzdata_state->random == true)
      {
	  random_init(&(vm_state->random_handle), RANDOM_DEFAULT_SRC);	//TODO: err chck bool
    } else
      {				/* won't get here */
      }

    if (target_state->socket == true)
      {
	  socket_launch(max_connections,
			target_state->socket_host,
			target_state->socket_port,
			target_state->socket_type, NULL);
      }

    while (1)
      {
	  perm = _get_next_permutation(&perm_len);
	  if (perm == NULL)
	      break;

	  _print_permutation(perm, perm_len);

	  if (target_state->command == true)
	    {
		//TODO: enforce should only be string fuzzing. cstring,unicode?
		perm[perm_len] = '\0';	//tie off c-string
		if (target_state->command_ind_dollar != -1 && target_state->command_stdin == false)	//is there a single $$ arg in target_command args  
		  {

		      LLIST_P dollar_arg =
			  llist_get_at_index(target_state->command_argv,
					     target_state->
					     command_ind_dollar);
		      if (dollar_arg != NULL)
			{
			    dollar_arg->element = checked_strdup((char *) perm);	//substitute the single "$$" arg
			    dollar_arg->size = perm_len;	//counting the \0
		      } else
			{	/* shouldn't get here */
			}

		      stdin_perm = NULL;
		      stdin_perm_len = 0;

		} else if (target_state->command_ind_dollar == -1
			   && target_state->command_stdin == true)
		  {
		      //stdin fuzzing 
		      stdin_perm = perm;
		      stdin_perm_len = perm_len;
		} else
		  {
		      fatal_error_println("Shouldn't have got here!");
		  }


		command_launch(target_state->command_argv,
			       stdin_perm, stdin_perm_len);
	  } else if (target_state->dl == true)
	    {
		if (target_state->dl_ind_dollar != -1)	/* is there a single $$ arg in target dl args ? */
		  {
		      LLIST_P dollar_arg =
			  llist_get_at_index(target_state->dl_args,
					     target_state->dl_ind_dollar);
		      if (dollar_arg != NULL)
			{
			    //substitute the single "$$" arg
			    struct st_dlarg *dlarg;
			    dlarg = dollar_arg->element;
			    dlarg->type = TYPE_GENERIC_PTR;	//might be a c string. might be binary
			    dlarg->ptr_len = perm_len;
			    dlarg->val.ptr_val =
				checked_strdup((char *) perm);
			    dollar_arg->size = sizeof(struct st_dlarg);
		      } else
			{	/* shouldn't get here */
			}
		  }
		//TODO: error check
		dl_launch(target_state->dl_args, target_state->dl_libfname,
			  target_state->dl_symbol);
	  } else if (target_state->socket == true)
	    {

	  } else
	    {			/* wont get here */
	    }


	  if (target_state->socket == false)
	    {
		while (glob.current_target_processes >= max_proc)
		  {
		      debug1_printf
			  ("Limit of target processes reached, sleeping 1 sec\n");
		      sleep(1);
		  }
	    }
/* Reason for vm_state->perm_count != 0 is so that the modulus does evaluate to true for 0 and print an additional hash mark */
	  if (debug_level == 0 && vm_state->perm_count != 0 && vm_state->perm_count % HASH_MARK_RATIO == 0)	//no -d or -q flags
	    {
		fprintf(stderr, "#");
	    }

	  vm_state->perm_count++;
      }				// end while loop


    //while loop ends when get_next_perm() returns NULL,

//processes may still be running however.
    debug_unquiet_printf
	("\n***** COMPLETED start statement! count=%ld*****\n",
	 vm_state->perm_count);

    //random_handle not reset
    //TODO: fix so it closes or reuses
    vm_state_reset();		// reset state
}


void stmt_process_flag(struct st_stmt_flag *stmt_flag)
{
    struct st_vm_flag_state *fs = glob.vm_state.flag_state;

    if (stmt_flag->termination_any == true)
	fs->termination_any = true;
    else if (stmt_flag->termination_signal_any == true)
	fs->termination_signal_any = true;
    else if (stmt_flag->termination_exit_any == true)
	fs->termination_exit_any = true;
    else if (stmt_flag->termination_signal)
	fs->termination_signals =
	    llist_append(&(stmt_flag->val), sizeof(int),
			 fs->termination_signals);
    else if (stmt_flag->termination_exit)
	fs->termination_exits =
	    llist_append(&(stmt_flag->val), sizeof(int),
			 fs->termination_exits);
}

void stmt_process_send(struct st_stmt_send *stmt_send)
{
    //TODO: create ASL of sends and expects.
    //TODO: stmt_process_start will deliver ASL via socket.c pipe writer
}

void stmt_process_expect(struct st_stmt_expect *stmt_expect)
{
    //TODO: create ASL of sends and expects.
}

void stmt_process_functioncall(char *symname)
{
    struct st_asl_cell *asl_cell = asl_get_asl_cell(symname);
    if (asl_cell == NULL)
      {
	  fprintf(stderr, "Reference to undeclared symbol: %s\n", symname);
	  return;		//TODO: maybe fatal error
      }
    asl_process_asl_cell(asl_cell);	//debug2prints asl_cell->symname
}

//print msg and a newline to stdout
void stmt_process_print(char *msg)
{
    printf("%s\n", msg);
    fflush(stdout);
}

void stmt_process_include(char *file)
{
}

void stmt_process_shell(char *command_str)
{
    if (system(command_str) < 0)
      {
	  fprintf(stderr, "system() failed: %s: Command: %s\n",
		  strerror(errno), command_str);
      }
}

void _print_permutation(uint8_t * perm, size_t perm_len)
{
    int i, c;
    float ratio_printable;

    if (debug_level < 1)
	return;

    for (i = 0, c = 0; i < perm_len; i++)
	if (isalnum(perm[i]))
	    c++;

    if (perm_len != 0)
	ratio_printable = ((float) c / (float) perm_len);
    else
	ratio_printable = 0.0;


    debug1_printf("Permutation #%ld : length[%d] : [ ",
		  glob.vm_state.perm_count + 1, perm_len);

    if (perm_len > 10 && ratio_printable < 0.75)
      {
	  debug1_printf("\n");
	  for (i = 0; i < perm_len; i++)
	      debug1_printf("%02X%c%c", perm[i], (i % 2 ? '\0' : ' '),
			    (i % 10 ? '\0' : '\n'));
	  debug1_printf("\n");

    } else
      {
	  for (i = 0; i < perm_len; i++)
	      if (isprint(perm[i]))
		  debug1_printf("%c", perm[i]);
	      else
		  debug1_printf("[0x%02X]", perm[i]);
      }
    debug1_printf(" ]\n");
}

uint8_t *_get_next_permutation(size_t * perm_len)
{
    uint8_t *perm = NULL;
    static char buf[32000];
    struct st_vm_state *vm_state = &(glob.vm_state);
    struct st_stmt_fuzzdata *fuzzdata_state = vm_state->fuzzdata_state;

    if (fuzzdata_state == NULL)	/* shouldnt happen */
	return NULL;

    if (fuzzdata_state->pattern != NULL)
      {
	  perm = regldg_next_permutation(perm_len);
    } else if (fuzzdata_state->file != NULL)
      {
	  perm = (uint8_t *) dictfile_next_permutation();
	  *perm_len = strlen((char *) perm);
    } else if (fuzzdata_state->random == true)
      {
	  perm = (uint8_t *) buf;
	  //TODO: move following to random.c
	  random_get_random_amount_of_bytes(&(vm_state->random_handle),
					    perm, perm_len,
					    fuzzdata_state->
					    random_length_lb,
					    fuzzdata_state->
					    random_length_ub);
      }
    return perm;
}
